#include "src/net/Buffer_v2.h"

#include "gtest/gtest.h"
#include <unistd.h> // For pipe

using namespace sola;

TEST(BufferTest, InitialState) {
    Buffer buf;
    EXPECT_EQ(buf.readableBytes(), 0);
    EXPECT_EQ(buf.writableBytes(), Buffer::kInitialSize);
    EXPECT_EQ(buf.prependableBytes(), Buffer::kCheapPrepend);
}

TEST(BufferTest, AppendAndRetrieve) {
    Buffer      buf;
    std::string str1 = "hello";
    buf.append(str1);
    EXPECT_EQ(buf.readableBytes(), 5);
    EXPECT_EQ(buf.retrieveAsString(5), str1);
    EXPECT_EQ(buf.readableBytes(), 0);

    std::string str2 = "world";
    buf.append(str2.data(), str2.size());
    EXPECT_EQ(buf.readableBytes(), 5);
    EXPECT_EQ(buf.retrieveAllAsString(), str2);
    EXPECT_EQ(buf.readableBytes(), 0);
}

TEST(BufferTest, BufferGrowth) {
    Buffer      buf;
    std::string big_str(1200, 'x');
    buf.append(big_str);
    EXPECT_EQ(buf.readableBytes(), 1200);
    EXPECT_GT(buf.internalCapacity(), Buffer::kInitialSize);
    EXPECT_EQ(buf.retrieveAllAsString(), big_str);
}

TEST(BufferTest, ReadAndMakeSpace) {
    Buffer buf;
    // Fill up the buffer almost completely
    std::string almost_full(Buffer::kInitialSize - 1, 'a');
    buf.append(almost_full);
    EXPECT_EQ(buf.readableBytes(), almost_full.size());
    EXPECT_EQ(buf.writableBytes(), 1);

    // Retrieve some data to create prependable space
    buf.retrieve(100);
    EXPECT_EQ(buf.readableBytes(), almost_full.size() - 100);
    EXPECT_EQ(buf.prependableBytes(), Buffer::kCheapPrepend + 100);

    // Append more data, which should trigger makeSpace() instead of resizing
    size_t old_capacity = buf.internalCapacity();
    // buf.append("some more data");
    buf.append(std::string("some more data"));
    EXPECT_EQ(buf.internalCapacity(), old_capacity); // Should not have grown
    EXPECT_EQ(buf.prependableBytes(), Buffer::kCheapPrepend);
}

TEST(BufferTest, Prepend) {
    Buffer buf;
    buf.append("world", 5);
    EXPECT_EQ(buf.readableBytes(), 5);

    int16_t header = 12345;
    buf.prependInt16(header);
    EXPECT_EQ(buf.readableBytes(), 7);
    EXPECT_EQ(buf.prependableBytes(), Buffer::kCheapPrepend - 2);

    EXPECT_EQ(buf.retrieveInt16(), header);
    EXPECT_EQ(buf.retrieveAllAsString(), "world");
}

TEST(BufferTest, FindEOLAndCRLF) {
    Buffer buf;
    buf.append(std::string("line1\nline2\r\nline3"));

    const char* eol = buf.findEOL();
    ASSERT_NE(eol, nullptr);
    EXPECT_EQ(*eol, '\n');
    EXPECT_EQ(std::string_view(buf.peek(), eol - buf.peek()), "line1");

    const char* crlf = buf.findCRLF();
    ASSERT_NE(crlf, nullptr);
    EXPECT_EQ(std::string(crlf, 2), "\r\n");
    EXPECT_EQ(std::string_view(buf.peek(), crlf - buf.peek()), "line1\nline2");
}

TEST(BufferTest, ReadFd) {
    Buffer buf;
    int    pipefd[2];
    ASSERT_EQ(pipe(pipefd), 0);

    const std::string data_to_write = "testing read fd";

    ssize_t n = write(pipefd[1], data_to_write.data(), data_to_write.size());
    ASSERT_EQ(n, data_to_write.size());
    close(pipefd[1]);

    int     savedErrno = 0;
    ssize_t bytes_read = buf.readFd(pipefd[0], &savedErrno);
    ASSERT_GT(bytes_read, 0);
    EXPECT_EQ(bytes_read, data_to_write.size());
    EXPECT_EQ(buf.retrieveAllAsString(), data_to_write);

    close(pipefd[0]);
}
